/*
 * © 2024 huayunliufeng保留所有权利, 依据MIT许可证发布。
 * 请勿更改或删除版权声明或此文件头。
 * 此代码是免费软件, 您可以重新分发和/或修改它。
 * 开源是希望它有用, 但不对代码做任何保证。
 * 如有疑问请联系: huayunliufeng@163.com
 */

package io.github.huayunliufeng.common.utils;

import org.apache.commons.lang3.StringUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

/**
 * 常用的字符串操作相关方法。
 *
 * @author zhongq
 * @datetime 2024/3/22 17:15
 */
final public class HylfStringUtil {
    /**
     * 判断指定字符是不是大小写字符或数字
     *
     * @param ch ch
     * @return true or false
     */
    public static boolean isLetterOrDigit(char ch) {
        return (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z');
    }

    /**
     * 蛇形字符串转换为大驼峰, 不是大小写字母和数字的字符会被忽略
     *
     * @param snakeStr 蛇形字符串
     * @return 大驼峰字符串
     */
    public static String snakeToGreatHumpStr(String snakeStr) {
        return snakeToHumpStr(snakeStr, true);
    }

    /**
     * 蛇形字符串转换为小驼峰, 不是大小写字母和数字的字符会被忽略
     *
     * @param snakeStr 蛇形字符串
     * @return 小驼峰字符串
     */
    public static String snakeToSmallHumpStr(String snakeStr) {
        return snakeToHumpStr(snakeStr, false);
    }

    /**
     * 蛇形字符串转换为大/小驼峰, 不是大小写字母和数字的字符会被忽略
     *
     * @param snakeStr  蛇形字符串
     * @param isBigHump 是否大驼峰
     * @return 大/小驼峰字符串
     */
    private static String snakeToHumpStr(String snakeStr, boolean isBigHump) {
        if (StringUtils.isBlank(snakeStr)) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        boolean isUpper = true;
        for (int i = 0; i < snakeStr.length(); i++) {
            char ch = snakeStr.charAt(i);
            if (ch == '_') {
                isUpper = true;
                continue;
            }
            // 对非大小写字符和数字忽略
            if (!isLetterOrDigit(ch)) {
                continue;
            }
            if (isUpper) {
                sb.append(Character.toUpperCase(ch));
                isUpper = false;
            } else {
                sb.append(Character.toLowerCase(ch));
            }
        }
        if (sb.length() == 0) {
            return "";
        }
        return isBigHump ? sb.toString() : sb.replace(0, 1, String.valueOf(Character.toLowerCase(sb.charAt(0))))
                .toString();
    }

    /**
     * 驼峰字符串转换为蛇形字符串, 不是大小写字母和数字的字符会被忽略
     *
     * @param humpStr 驼峰字符串
     * @return 蛇形字符串
     */
    public static String humpToSnakeStr(String humpStr) {
        if (StringUtils.isBlank(humpStr)) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        boolean flag = false;
        for (int i = 0; i < humpStr.length(); i++) {
            char ch = humpStr.charAt(i);
            if (!isLetterOrDigit(ch)) {
                continue;
            }
            if (Character.isUpperCase(ch)) {
                if (flag) {
                    sb.append('_');
                    flag = false;
                }
            }
            if (Character.isLowerCase(ch)) {
                flag = true;
            }
            sb.append(Character.toLowerCase(ch));
        }
        return sb.toString();
    }

    /**
     * 使用KMP算法获取模式字符串在目标字符串中第一次出现的位置
     *
     * @param targetStr  目标字符串
     * @param patternStr 模式字符串
     * @return -1 表示没找到或其中一个字符串为空, 否则返回第一次出现的索引
     */
    public static int indexOf(String targetStr, String patternStr) {
        if (StringUtils.isEmpty(targetStr) || StringUtils.isEmpty(patternStr)) {
            return -1;
        }
        return indexOf(targetStr.toCharArray(), patternStr.toCharArray());
    }

    /**
     * 使用KMP算法获取模式字符数组在目标字符数组中第一次出现的位置
     *
     * @param target  目标字符数组
     * @param pattern 模式字符数组
     * @return -1 表示没找到或其中一个字符数组为空, 否则返回第一次出现的索引
     */
    public static int indexOf(char[] target, char[] pattern) {
        if (HylfCollectionUtil.isEmpty(target) || HylfCollectionUtil.isEmpty(pattern)) {
            return -1;
        }
        int n = target.length;
        int m = pattern.length;
        int[] next = getNextArray(pattern);
        for (int i = 0, j = 0; i < n; i++) {
            if (j > 0 && target[i] != pattern[j]) {
                j = next[j - 1];
            }
            if (target[i] == pattern[j]) {
                j++;
            }
            if (j == m) {
                return i - j + 1;
            }
        }
        return -1;
    }

    /**
     * 使用KMP算法获取模式字符串在目标字符串中最后一次出现的位置
     *
     * @param targetStr  目标字符串
     * @param patternStr 模式字符串
     * @return -1 表示没找到或其中一个字符串为空, 否则返回最后一次出现的索引
     */
    public static int lastIndexOf(String targetStr, String patternStr) {
        if (StringUtils.isEmpty(targetStr) || StringUtils.isEmpty(patternStr)) {
            return -1;
        }
        return lastIndexOf(targetStr.toCharArray(), patternStr.toCharArray());
    }

    /**
     * 使用KMP算法获取模式字符数组在目标字符数组中最后一次出现的位置
     *
     * @param target  目标字符数组
     * @param pattern 模式字符数组
     * @return -1 表示没找到或其中一个字符数组为空, 否则返回最后一次出现的索引
     */
    public static int lastIndexOf(char[] target, char[] pattern) {
        if (HylfCollectionUtil.isEmpty(target) || HylfCollectionUtil.isEmpty(pattern)) {
            return -1;
        }
        int n = target.length;
        int m = pattern.length;
        int[] next = getNextArray(pattern);
        int index = -1;
        for (int i = 0, j = 0; i < n; i++) {
            if (j > 0 && target[i] != pattern[j]) {
                j = next[j - 1];
            }
            if (target[i] == pattern[j]) {
                j++;
            }
            if (j == m) {
                index = i - j + 1;
                j = next[j - 1];
            }
        }
        return index;
    }

    /**
     * 使用KMP算法获取模式字符串在目标字符串中出现的次数, 注意, 例如 target = "aaaa", pattern = "aa" 这种，返回结果是2
     *
     * @param targetStr  目标字符串
     * @param patternStr 模式字符串
     * @return 出现次数
     */
    public static int count(String targetStr, String patternStr) {
        if (StringUtils.isEmpty(targetStr) || StringUtils.isEmpty(patternStr)) {
            return -1;
        }
        return count(targetStr.toCharArray(), patternStr.toCharArray());
    }

    /**
     * 使用KMP算法获取模式字符数组在目标字符数组中出现的次数, 注意, 例如 target = "aaaa", pattern = "aa" 这种，返回结果是2
     *
     * @param target  目标字符数组
     * @param pattern 模式字符数组
     * @return 出现次数
     */
    public static int count(char[] target, char[] pattern) {
        if (HylfCollectionUtil.isEmpty(target) || HylfCollectionUtil.isEmpty(pattern)) {
            return -1;
        }
        int n = target.length;
        int m = pattern.length;
        int[] next = getNextArray(pattern);
        int count = 0;
        for (int i = 0, j = 0; i < n; i++) {
            if (j > 0 && target[i] != pattern[j]) {
                j = next[j - 1];
            }
            if (target[i] == pattern[j]) {
                j++;
            }
            if (j == m) {
                count++;
                j = 0;
            }
        }
        return count;
    }

    /**
     * 使用KMP算法获取模式字符串在目标字符数组中出现的所有位置, 注意, 例如 target = "aaaa", pattern = "aa" 这种，返回结果是0,1,2
     *
     * @param target  目标字符数组
     * @param pattern 模式字符数组
     * @return 出现的所有位置
     */
    public static int[] indexArray(char[] target, char[] pattern) {
        if (HylfCollectionUtil.isEmpty(target) || HylfCollectionUtil.isEmpty(pattern)) {
            return new int[0];
        }
        int n = target.length;
        int m = pattern.length;
        int[] next = getNextArray(pattern);
        List<Integer> indexList = new ArrayList<>();
        for (int i = 0, j = 0; i < n; i++) {
            if (j > 0 && target[i] != pattern[j]) {
                j = next[j - 1];
            }
            if (target[i] == pattern[j]) {
                j++;
            }
            if (j == m) {
                indexList.add(i - j + 1);
                j = next[j - 1];
            }
        }
        return indexList.stream().mapToInt(Integer::intValue).toArray();
    }

    /**
     * 使用KMP算法获取模式字符串在目标字符串中出现的所有位置, 注意, 例如 target = "aaaa", pattern = "aa" 这种，返回结果是0,1,2
     *
     * @param targetStr  目标字符串
     * @param patternStr 模式字符串
     * @return 出现的所有位置
     */
    public static int[] indexArray(String targetStr, String patternStr) {
        if (StringUtils.isEmpty(targetStr) || StringUtils.isEmpty(patternStr)) {
            return new int[0];
        }
        return indexArray(targetStr.toCharArray(), patternStr.toCharArray());
    }

    private static int[] getNextArray(char[] chars) {
        if (HylfCollectionUtil.isEmpty(chars)) {
            return new int[0];
        }
        int n = chars.length;
        int[] next = new int[n];
        for (int i = 1, j = 0; i < n; i++) {
            if (j > 0 && chars[i] != chars[j]) {
                j = next[j - 1];
            }
            if (chars[i] == chars[j]) {
                j++;
            }
            next[i] = j;
        }
        return next;
    }

    /**
     * 将指定字符串的首字母大写, 如果传入null或空字符串或空白字符串, 返回空字符串
     *
     * @param str str
     * @return 首字母大写的字符串
     */
    public static String capFirstLetter(String str) {
        if (StringUtils.isBlank(str)) {
            return "";
        }
        return str.substring(0, 1).toUpperCase(Locale.ROOT) + str.substring(1);
    }

    /**
     * 确保地址或路径的最后有一个‘/’
     *
     * @param path path
     * @return String
     * @since 0.0.7-3
     */
    public static String getSuffixUrlPath(String path) {
        if (StringUtils.isEmpty(path)) {
            return "/";
        }
        if (path.charAt(path.length() - 1) != '/') {
            return path + "/";
        }
        return path;
    }

    /**
     * 确保地址或路径的开头有一个‘/’
     *
     * @param path path
     * @return String
     * @since 0.0.7-3
     */
    public static String getPrefixUrlPath(String path) {
        if (StringUtils.isEmpty(path)) {
            return "/";
        }
        if (path.charAt(0) != '/') {
            return "/" + path;
        }
        return path;
    }

    /**
     * 确保地址或路径的开头和结尾都有一个‘/’
     *
     * @param path path
     * @return String
     * @since 0.0.7-3
     */
    public static String getUrlPath(String path) {
        return getSuffixUrlPath(getPrefixUrlPath(path));
    }
}
